#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "./c_lib/uthash/uthash.h"
#include "./c_src/memory_allocation.h"
#include "./c_src/random.h"
#include "./c_src/log.h"
#include "./c_src/set.h"
#include "runtime.h"
#define MEMORY_BLOCKS CACHE_SIZE
#define DEBUG 0

void PyInit_runtime(void) {
    // do stuff...
}

int64_t initialized = 0;

struct Runtime
{
    int64_t memptr;
    int64_t mem[MEMORY_BLOCKS];
};

struct Runtime runtime;
struct MemoryCache autoptr_memcache;



//void print_stacktrace(void)
//{
//    int size = 16;
//    void *array[16];
//    int stack_num = backtrace(array, size);
//    char **stacktrace = backtrace_symbols(array, stack_num);
//    for (int i = 0; i < stack_num; ++i)
//    {
//        printf("%s\n", stacktrace[i]);
//    }
//    free(stacktrace);
//}

int64_t pm_runtime_init()//int64_t a)
{
    if (DEBUG)
    {
        log_set_level(LOG_DEBUG);
    }
    else
    {
        log_set_level(LOG_INFO);
    }

    log_debug("Runtime init!");
    runtime.memptr = 0;
    for (int i = 0; i < MEMORY_BLOCKS; i++)
    {
        runtime.mem[i] = 0;
    }
    initialized = 1;
    init_seed();
    mem_cache_init(&autoptr_memcache);
    intset_init_cache();
    log_debug("Initialization finished!");
    return 0;
}

int64_t get_refcount_value(int64_t addr_index)
{
    int64_t addr_value = 0;
    if ((0 <= addr_index) && (addr_index < MEMORY_BLOCKS))
    {
        addr_value = runtime.mem[addr_index];
        if (addr_value < 0)
        {
            return -1000;
        }else if (addr_value==0){
            return -2000;
        }
        return *(int64_t *)addr_value;
    }
    else
    {
        char *err = "Index in refcount value was invalid!";
        raise_error(err);
        return -1;
    }
}

int64_t get_free_memory_index(void)
{
    for (int i = runtime.memptr; i < MEMORY_BLOCKS; i++)
    {
        if (runtime.mem[i] == 0)
        {
            runtime.memptr = i;
            return i;
        }
    }
    printf("free memory index.....%ld \n", runtime.memptr);

    for (int i = 0; i <= runtime.memptr; i++)
    {
        if (runtime.mem[i] == 0)
        {
            runtime.memptr = i;
            return i;
        }else if(runtime.mem[i]<0){
//            printf("invalid value, i: %ld, val: %ld", i, runtime.mem[i]);
        }
    }
    printf("free memory index.....2 %ld %ld\n", runtime.memptr, runtime.mem[0]);
    return -1;
}

void free_auto_ptr(int64_t block_index)
{
    // 智能指针的地址
    int64_t auto_ptr_addr_value = runtime.mem[block_index];

    // 智能指针的第二位保存了数组或对象的数据部分的指针，需要偏移8字节。
    // 这里是地址的数值。
    int64_t address = auto_ptr_addr_value + sizeof(int64_t);
    int64_t weakref = *(int64_t *)(auto_ptr_addr_value + sizeof(int64_t) * 2);
    int64_t size_in_8_bytes = *(int64_t *)(auto_ptr_addr_value + sizeof(int64_t) * 4);
    // printf("size_in_8_byts %ld\n", size_in_8_bytes);
    void **ptr = (void *)(void *)address;

    //    printf("Print the pointers. weakref: %ld, block_id: %ld, %p, %p\n",
    //           weakref, block_index, (void *)auto_ptr_addr_value, *ptr);

    if (weakref == 0)
    {
        free(*ptr);
        *ptr = NULL;
        //        printf("Released body\n");
    }
    // 释放智能指针的内存
    void *ptr_auto = (void *)auto_ptr_addr_value;
    if (mem_cache_add(&autoptr_memcache, ptr_auto) == 0)
    {
        free(ptr_auto);
    }
    ptr_auto = NULL;
    //    printf("Released auto ptr\n");
}

int64_t gc(void)
{
    int64_t addr_value = 0;
    int64_t ref_count = 0;
    int64_t released_count = 0;
    for (int i = 0; i < MEMORY_BLOCKS; i++)
    {
        addr_value = runtime.mem[i];
        if (addr_value > 0)
        {
            ref_count = get_refcount_value(i);
            // 如果引用计数为0，就将其清空。
            if (ref_count == 0)
            {
                free_auto_ptr(i);
                runtime.mem[i] = 0;
                released_count += 1;
            }

        }
    }
    return released_count;
}

char *pm_mem_allocate(int64_t bytes)
{
    char *ptr = (char *)malloc(bytes);
    for (int64_t i = 0; i < bytes; i++)
    {
        ptr[i] = 0;
    }
    log_debug("Allocated memory at address: %ld, size %ld\n", (int64_t)ptr, bytes);
    return ptr;
}

char *pm_mem_reallocate(char *allocated, int64_t new_bytes, int64_t old_bytes)
{
    char *new_ptr = (char *)malloc(new_bytes);
    for (int64_t i = 0; i < new_bytes; i++)
    {
        if (i < old_bytes)
        {
            new_ptr[i] = allocated[i];
        }
        else
        {
            new_ptr[i] = 0;
        }
    }
    free(allocated);
    allocated = NULL;
    log_debug("Re-Allocated memory at address: %ld, size %ld -> %ld\n", (int64_t)new_ptr, old_bytes, new_bytes);
    return new_ptr;
}

char *pm_auto_ptr_allocate(int64_t auto_ptr_bytes)
{
    log_debug("trying to find free memory\n");
    int64_t free_memory_index = get_free_memory_index();
    int64_t released_objs = 0;
    if (free_memory_index == -1)
    {
        log_debug("GC started ...\n");
        released_objs = gc();
        log_debug("GC collected %ld items\n", released_objs);
        free_memory_index = get_free_memory_index();
        if (DEBUG)
        {
            for (int64_t i = 0; i < MEMORY_BLOCKS; i++)
            {
                printf("%ld:%ld ", i, get_refcount_value(i));
            }
        }
        printf("autoptr_addr %ld\n", runtime.memptr);
        printf("free mem index %ld\n", get_free_memory_index());
        log_debug("Memory index after gc: %ld\n", free_memory_index);
    }
    if (free_memory_index == -1)
    {
        log_error("\033[31m\nCannot allocate more memory\nThe Heap allocation table was: \n");
        for (int64_t i = 0; i < MEMORY_BLOCKS; i++)
        {
            printf("%ld:%ld ", i, get_refcount_value(i));
        }
        exit(1);
    }
    // printf("Trying to malloc %ld bytes to slot %ld\n", auto_ptr_bytes, free_memory_index);
    void *ptr1 = NULL;

//    ptr1 = mem_cache_get(&autoptr_memcache);
//    if (ptr1 == NULL)
//    {
        ptr1 = malloc((size_t)40);
//    }
    runtime.mem[free_memory_index] = (int64_t)ptr1;
    return (char *)ptr1;
}

void print_double(double value)
{
    printf("%f ", value);
}

void print_integer(int64_t value)
{
    printf("%ld ", value);
}

void print_string(char *value)
{
    printf("%s ", value);
}

void print_lf(void)
{
    printf("\n");
}

void pm_raise_index_error(int64_t low, int64_t high, int64_t index)
{
    log_fatal("\033[31m\nIndex error occurred: %ld is not in range [%ld, %ld)\n", index, low, high);
//    print_stacktrace();
    exit(1);
}

void raise_error(char *value)
{
    log_fatal("\033[31m\nAn error in PMBoost occurred: %s\n", value);
//    print_stacktrace();
    exit(1);
}
